/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  http://www.espertech.com                                                           *
 *  ---------------------------------------------------------------------------------- *
 *  The software in this package is published under the terms of the GPL license       *
 *  a copy of which has been included with this distribution in the license.txt file.  *
 ***************************************************************************************
 */
package com.espertech.esper.common.internal.event.bean.introspect;

import com.espertech.esper.common.client.configuration.ConfigurationException;
import com.espertech.esper.common.client.configuration.common.ConfigurationCommonEventTypeBean;
import com.espertech.esper.common.internal.event.bean.core.PropertyStem;
import com.espertech.esper.common.internal.event.core.EventPropertyType;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

/**
 * Introspector that considers explicitly configured event properties only.
 */
public class PropertyListBuilderExplicit implements PropertyListBuilder {
    private ConfigurationCommonEventTypeBean legacyConfig;

    /**
     * Ctor.
     *
     * @param legacyConfig is a legacy type specification containing
     *                     information about explicitly configured fields and methods
     */
    public PropertyListBuilderExplicit(ConfigurationCommonEventTypeBean legacyConfig) {
        if (legacyConfig == null) {
            throw new IllegalArgumentException("Required configuration not passed");
        }
        this.legacyConfig = legacyConfig;
    }

    public List<PropertyStem> assessProperties(Class clazz) {
        List<PropertyStem> result = new LinkedList<PropertyStem>();
        getExplicitProperties(result, clazz, legacyConfig);
        return result;
    }

    /**
     * Populates explicitly-defined properties into the result list.
     *
     * @param result       is the resulting list of event property descriptors
     * @param clazz        is the class to introspect
     * @param legacyConfig supplies specification of explicit methods and fields to expose
     */
    protected static void getExplicitProperties(List<PropertyStem> result,
                                                Class clazz,
                                                ConfigurationCommonEventTypeBean legacyConfig) {
        for (ConfigurationCommonEventTypeBean.LegacyFieldPropDesc desc : legacyConfig.getFieldProperties()) {
            result.add(makeDesc(clazz, desc));
        }
        for (ConfigurationCommonEventTypeBean.LegacyMethodPropDesc desc : legacyConfig.getMethodProperties()) {
            result.add(makeDesc(clazz, desc));
        }
    }

    private static PropertyStem makeDesc(Class clazz, ConfigurationCommonEventTypeBean.LegacyMethodPropDesc methodDesc) {
        Method[] methods = clazz.getMethods();
        Method method = null;
        for (int i = 0; i < methods.length; i++) {
            if (!methods[i].getName().equals(methodDesc.getAccessorMethodName())) {
                continue;
            }
            if (methods[i].getReturnType() == void.class) {
                continue;
            }
            if (methods[i].getParameterTypes().length >= 2) {
                continue;
            }
            if (methods[i].getParameterTypes().length == 0) {
                method = methods[i];
                break;
            }

            Class parameterType = methods[i].getParameterTypes()[0];
            if ((parameterType != int.class) && ((parameterType != Integer.class)) &&
                    (parameterType != String.class)) {
                continue;
            }

            method = methods[i];
            break;
        }

        if (method == null) {
            throw new ConfigurationException("Configured method named '" +
                    methodDesc.getAccessorMethodName() + "' not found for class " + clazz.getName());
        }

        return makeMethodDesc(method, methodDesc.getName());
    }

    private static PropertyStem makeDesc(Class clazz, ConfigurationCommonEventTypeBean.LegacyFieldPropDesc fieldDesc) {
        Field field;
        try {
            field = clazz.getField(fieldDesc.getAccessorFieldName());
        } catch (NoSuchFieldException ex) {
            throw new ConfigurationException("Configured field named '" +
                    fieldDesc.getAccessorFieldName() + "' not found for class " + clazz.getName());
        }
        return makeFieldDesc(field, fieldDesc.getName());
    }

    /**
     * Makes a simple-type event property descriptor based on a reflected field.
     *
     * @param field is the public field
     * @param name  is the name of the event property
     * @return property descriptor
     */
    protected static PropertyStem makeFieldDesc(Field field, String name) {
        return new PropertyStem(name, field, EventPropertyType.SIMPLE);
    }

    /**
     * Makes an event property descriptor based on a reflected method, considering
     * the methods parameters to determine if this is an indexed or mapped event property.
     *
     * @param method is the public method
     * @param name   is the name of the event property
     * @return property descriptor
     */
    protected static PropertyStem makeMethodDesc(Method method, String name) {
        EventPropertyType propertyType;

        if (method.getParameterTypes().length == 1) {
            Class parameterType = method.getParameterTypes()[0];
            if (parameterType == String.class) {
                propertyType = EventPropertyType.MAPPED;
            } else {
                propertyType = EventPropertyType.INDEXED;
            }
        } else {
            propertyType = EventPropertyType.SIMPLE;
        }

        return new PropertyStem(name, method, propertyType);
    }
}
